#include "common.h"
#include "Scanner.h"

#include "Identifier.h"
#include "Whitespace.h"
#include "Special.h"
#include "Integer.h"
#include "Real.h"
#include "String.h"

#include <fstream>

using std::ifstream;

LinkedList<TokenExtractor*>* Scanner::extractors = nullptr;

Scanner::Scanner(string& file_name)
{
	source_file = file_name;

	if(!extractors) initialize();
	ifstream file(source_file, std::ios::binary);
	int last_line_start = 0;
	int line_number = 1;
	
	LinkedList<Token*>* tokens = new LinkedList<Token*>;

	function<void(IdentifierGroup*&)> merger = [&](IdentifierGroup* val){
		val->appendLineNumber(line_number);
	};
	identifiers = new BinTree<IdentifierGroup*>(merger);

	while(file.good() && !file.eof())
	{
		char ch = file.get();

		TokenExtractor* extr = extractors->select([ch](TokenExtractor* in){ return in->getRecognizer()(ch); });
		Token* tok = extr->getExtractor()(ch, file);
		bool ws = dynamic_cast<Whitespace*>(tok) != nullptr;
		if(ws)
		{
			int pos = file.tellg();
			file.seekg(last_line_start);
			char* str = new char[pos - last_line_start + 1];
			str[pos - last_line_start] = '\0';
			file.read(str, pos - last_line_start);
			lines.append(new SourceLine(string(str), tokens));
			tokens = new LinkedList<Token*>;
			delete str;
			last_line_start = pos;
			++line_number;
		}

		Identifier* id = dynamic_cast<Identifier*>(tok);
		if(id)
		{
			identifiers->insert(new IdentifierGroup(id->getTokenString(), line_number));
		}

		if(tok && !ws) tokens->append(tok);
	}
}

void Scanner::initialize()
{
	extractors = new LinkedList<TokenExtractor*>;
	extractors->append(Identifier::getExtractor());
	extractors->append(Whitespace::getExtractor());
	extractors->append(Real::getExtractor());
	extractors->append(String::getExtractor());
	extractors->append(Special::getExtractor());
}

const LinkedList<SourceLine*>* Scanner::getLines() const
{
	return &lines;
}

string Scanner::getSourceFileName() const
{
	return source_file;
}

BinTree<IdentifierGroup*>* Scanner::getIdentifiers() const
{
	return identifiers;
}

SourceLine::SourceLine(string source, LinkedList<Token*>* tokens)
{
	this->source = source;
	this->tokens = tokens;
}

string SourceLine::getSourceLine() const
{
	return source;
}

LinkedList<Token*>* SourceLine::getTokens() const
{
	return tokens;
}